package edu.tamu.cse.teamproject1.Database;

import java.util.ArrayList;
import java.util.List;

import edu.tamu.cse.teamproject1.Database.Exceptions.NullReferenceException;
import edu.tamu.cse.teamproject1.Database.Exceptions.SqlException;
import edu.tamu.cse.teamproject1.Database.Interfaces.DatabaseAPI;
import edu.tamu.cse.teamproject1.Database.Interfaces.IColumnDefinition;
import edu.tamu.cse.teamproject1.Database.Interfaces.IDatabaseTable;
import edu.tamu.cse.teamproject1.Database.Interfaces.ITuple;
import edu.tamu.cse.teamproject1.Database.Parser.QueryParser;

/**
 * The class Database creates a Database in memory.
 * The database includes its name and a list of tables
 * (and queries) in the database.
 * 
 * @author Benjamin Unsworth
 * @author Christopher Weldon <chris@chrisweldon.net>
 * @since Release 0.1.0
 */
public class Database implements DatabaseAPI {
	/**
	 * Name of the database
	 */
	private String name;
	
	/**
	 * List of tables in the database
	 */
	private List<IDatabaseTable> tables;
	
	/**
	 * Retrieves a database table based on it's name
	 * 
	 * @param tableName
	 * @return table
	 */
	protected IDatabaseTable getTableByName(String tableName) {
		IDatabaseTable table = null;
		boolean foundTable = false;
		/*loops through all the tables till it finds the correct table*/
		for (IDatabaseTable tempTable : tables) {
			if (tempTable.getName() == tableName) {
				table = tempTable;
				break;
			}
		}
		if(!foundTable) throw new IllegalAccessError("Could not find the table: " + tableName);
		
		return table;
	}
	
	/**
	 * Creates a new database object
	 */
	public Database() {
		this.tables = new ArrayList<IDatabaseTable>();
	}
	
	/**
	 * Creates a new database object
	 * 
	 * @param databaseName
	 */
	public Database(String databaseName) {
		name = databaseName;
		this.tables = new ArrayList<IDatabaseTable>();
	}
	
	/**
	 * Sets the name for the database
	 * 
	 * @param databaseName
	 */
	@Override
	public void setDatabaseName(String databaseName) {
		name = databaseName;
	}
	
	/**
	 * Retrieves the name of the database
	 * 
	 * @return name
	 */
	@Override
	public String getDatabaseName() {
		return name;
	}
	
	/**
	 * Creates a database table and adds to the database
	 * 
	 * Some basic validation checks will occur before the new table is created, 
	 * such as making sure the list of column definitions are not null and have
	 * more than one column as well as making sure we're not trying to add a 
	 * table with the same name as an existing one. 
	 * 
	 * @param 	String tableName Name of the new table
	 * @param	ArrayList<IColumnDefinition> tupleNames Definitions of the columns
	 * @return void
	 * @throws	IllegalArgumentException if the table already exists
	 */
	@Override
	public void createTable(String tableName,
			ArrayList<IColumnDefinition> tupleNames) {
		
		/*no tuples to create the table with*/
		if (tupleNames == null) {
			throw new IllegalArgumentException("tupleNames cannot be null.");
		}
		
		// Try to get a table with a matching name. Fail if it exists
		IDatabaseTable matchingTable = this.getTableByName(tableName);
		if (matchingTable != null) {
			throw new IllegalArgumentException("table with this name already exists.");
		}
		
		/*create the new table and return it*/
		IDatabaseTable newTable = new Table(tableName, tupleNames);
		tables.add(newTable);
	}

	/**
	 * Deletes tuple records from a table with a given where clause
	 * 
	 * @param	String tableName Table to delete records from
	 * @param 	String whereClause Filter to apply on what records to delete
	 * @throws SqlException
	 */
	@Override
	public void delete(String tableName, String whereClause) throws SqlException {
		/*find the table to delete*/
		IDatabaseTable table = this.getTableByName(tableName);
		
		if (table == null) {
			throw new SqlException("Could not find table " + tableName);
		}
		
		QueryParser parser = new QueryParser();
		try {
			parser.deleteQuery(table, whereClause);
		} catch (SqlException e) {
			throw e;
		} catch (NullReferenceException e) {
			throw new SqlException("Unable to process delete request.", e);
		}
	}

	/**
	 * Drops a database table
	 * 
	 * This method is simple in that it takes the table name as the only argument
	 * and drops the table (and all tuples in that table) from the database. It 
	 * will proceed to update the XML Persistence document to show that the table
	 * has been dropped.
	 * 
	 * @param 	String tableName the name of the table to be dropped
	 * @return 	void
	 * @throws	SqlException if table could not be found
	 * @todo 	Update the XML Persistence Document
	 */
	@Override
	public void dropTable(String tableName) throws SqlException {
		/*find the table to be removed*/
		IDatabaseTable table = this.getTableByName(tableName);
		
		if (table == null) {
			throw new SqlException("Could not find table " + tableName);
		}
		
		tables.remove(table);
	}

	/**
	 * Inserts a tuple into the database
	 * 
	 * This method will take a tuple and insert into the list of tuples in the
	 * database. It performs some basic validation prior to inserting the tuple
	 * into the list. 
	 * 
	 * @param	String tableName Name of table to insert tuple
	 * @param	ITuple tuple Tuple to be inserted into the table
	 * @return	void
	 * @throws	SqlException if table could not be found
	 *
	 */
	@Override
	public void insert(String tableName, ITuple tuple) throws SqlException {
		IDatabaseTable table = this.getTableByName(tableName);
		
		if (table == null) {
			throw new SqlException("Could not find table " + tableName);
		} else if (tuple == null) {
			throw new SqlException("tuple cannot be null.");
		}
		
		table.addTuple(tuple);
	}

	/**
	 * Selects records from the database based on a SQL Query
	 * 
	 * The query string can have aliasing, joining, and filtering based on where
	 * clauses. The return is a in-moment snapshot of the database table records
	 * (as a view), complete with all columns named as they were requested in the
	 * query (aliasing), combined with other columns from other tables (if joining)
	 * and containing only the records that match the where clause(s). 
	 * 
	 * Example queries that can be passed into this method include:
	 * 
	 *  - SELECT * FROM tableName;
	 *  - SELECT * FROM tableName WHERE columnName = columnValue;
	 *  - SELECT columnName AS columnAlias, column2Name as column2Alias,
	 *      FROM tableName, table2Name WHERE columnAlias != columnValue;
	 *  - etc.
	 * 
	 * @param 	String query
	 * @return	A view containing all records and column definitions based on the query
	 */
	@Override
	public IDatabaseTable select(String query) throws SqlException {
		// Fetch the table(s) from the query name
		QueryParser parser = new QueryParser();
		parser.setDatabaseTables(tables);
		
		try {
			IDatabaseTable resultingView = parser.selectQuery(query);
			return resultingView;
		} catch (NullReferenceException e) {
			throw new SqlException("An unexpected exception has occurred. Please try again.");
		} catch (SqlException e) {
			throw e;
		}
	}

	/**
	 * Updates tuples in a particular database table
	 * 
	 * @param	String tableName Table to update records in
	 * @param	ArrayList<ITuple> tuples Tuples to update in the database
	 */
	@Override
	public void update(String tableName, ArrayList<ITuple> tuples) throws SqlException {
		IDatabaseTable table = this.getTableByName(tableName);
		if (table == null) {
			throw new SqlException("Could not find table " + tableName);
		}
		
		// Search for tuples and drop, insert to give the sense that we're updating them
		// CAUTION: O(n^2)
		for (ITuple tuple : table.getAllTuples()) {
			for (ITuple tupleToUpdate : tuples) {
				if (tupleToUpdate.getUniqueId() == tuple.getUniqueId()) {
					table.deleteTuple(tuple);
					table.addTuple(tupleToUpdate);
				}
			}
		}
	}

	
	/**
	 * Adds a list of tables to the Database
	 * @param tables
	 */
	@Override
	public void addListOfTables(List<IDatabaseTable> tableList){
		for(IDatabaseTable table: tableList){
			tables.add(table);
		}
	}

	/**
	 * Adds a table to the list of tables
	 */
	@Override
	public void addATable(IDatabaseTable table) {
		tables.add(table);
	}

	/**
	 * Retrieves the list of tables
	 * @return tables
	 */
	@Override
	public List<IDatabaseTable> getTableList() {
		return tables;
	}
	
	
}
